/*
 * File:   Shell.cpp
 * Author: henrya
 *
 * Created on June 13, 2009, 6:58 PM
 */


#include "Shell.h"
#include "RuntimeException.h"
#include "ThreadCreationException.h"
#include "Tokenizer.h"
#include "IOException.h"
#include "RoundRobinScheduler.h"
#include "MultiLevelScheduler.h"
#include "SystemMessage.h"
#include "Message.h"
#include "Node.h"
#include "Network.h"

#include <iostream>
#include <cstdlib>
using std::cout;
using std::ifstream;
using std::endl;
using std::cin;


extern "C" {

    void* shell_wrapper_func(void *args) {
        Shell *curShell = (Shell *) args;
        curShell->run();
        pthread_exit(NULL);
    }
}

Shell::Shell() : interupted(false),mailer(NULL),network(NULL),sleeping(false) {
    if (pthread_mutex_init(&classLock, NULL) != 0) {
        perror("Shell lock initialization");
        exit(1);
    }
    if (pthread_cond_init(&classCond, NULL) != 0) {
        perror("Shell cond var initialization");
        exit(1);
    }
}

Shell::~Shell() {
    pthread_mutex_destroy(&classLock);
    pthread_cond_destroy(&classCond);
}

void Shell::run() {
    while (!interupted) {
        cout << "> ";
        string in;
        getline(cin, in);
        Tokenizer tknzr(in);
        if (!tknzr.hasNext()) {
            continue;
        }
        string command = *tknzr.next();
        if (command == "q") {
            interrupt();
        } else {
            if (command == "CreateNet" || "cn" == command) {
                try {
                    doCreateNet(tknzr.next());
                } catch (RuntimeException ex) {
                    cout<< ex.what() << endl;
                }
            } else {
                if ("run" == command) {
                    if (mailer != NULL) {
                        if (tknzr.hasNext()) {
                            switch (atoi((*tknzr.next()).c_str())) {
                            case 1: {
                                if (tknzr.hasNext()) {
                                    mailer->setScheduler(new RoundRobinScheduler(network,atoi((*tknzr.next()).c_str())));
                                } else {
                                    cout<<"Usage: run 1 <quanta>"<<endl;
                                    continue;
                                }
                            }
                            break;
                            case 2: {
                                if (tknzr.hasNext()) {
                                    mailer->setScheduler(new MultiLevelScheduler(network,atoi((*tknzr.next()).c_str())));
                                } else {
                                    cout<<"Usage: run 2 <quanta>"<<endl;
                                    continue;
                                }
                            }
                            break;
                            }
                            network->startNodes();
                            mailer->start();
                            mailer->setMode(Mailer::DVR);
                            pthread_mutex_lock(&classLock);
                            mailer->wakeUp();
                            goToSleep();
                        } else {
                            cout<<"Usage: run <algo_number> [optional args]"<<endl;
                        }
                    } else {
                        cout << "No network defined" << endl;
                    }
                } else {
                    if ("SendPacket" == command || "sp" == command) {
                        if (mailer != NULL && mailer->hasRunned()) {
                            if (tknzr.hasNext()) {
                                int src=atoi((*tknzr.next()).c_str());
                                if (tknzr.hasNext()) {
                                    int dest=atoi((*tknzr.next()).c_str());
                                    if (tknzr.hasNext()) {
                                        string* msgText=tknzr.next();
                                        pthread_mutex_lock(&classLock);
                                        Message* msg=new SystemMessage(SystemMessage::SEND_PACKET,msgText);
                                        msg->setFinalDest(dest);
                                        msg->setTo(src);
                                        msg->setFrom(msgText->length());
                                        mailer->tryAddMessage(msg);
                                        mailer->wakeUp();
                                        goToSleep();
                                    } else {
                                        cout<<"Usage: SendPacket <source_node> <dest_node> <msg>"<<endl;
                                    }
                                } else {
                                    cout<<"Usage: SendPacket <source_node> <dest_node> <msg>"<<endl;
                                }
                            } else {
                                cout<<"Usage: SendPacket <source_node> <dest_node> <msg>"<<endl;
                            }
                        } else {
                            cout << "No network defined or DVR not runned." << endl;
                        }
                    } else {
                        if (command == "Exit") {
                            if (mailer!=NULL)
                                mailer->interrupt();
                        }
                        if (network!=NULL) {
                            network->killAll();
                        }
                        interupted=true;
                    }
                }
            }
        }
    }
    pthread_exit(NULL);
}

void Shell::interrupt() {
    interupted = true;
}

void Shell::join() {
    if (pthread_join(self, NULL) != 0) {
        RuntimeException ex("Unable to join thread " + self);
        throw ex;
    }
}

void Shell::doCreateNet(string* filename) {
    if (filename == NULL) {
        RuntimeException ex("Usage: CreateNet <config_file_name>");
        throw ex;
    }
    ifstream netConfStream((*filename).c_str(), ios::in);
    if (netConfStream == NULL) {
        IOException ex("Unable to open file: " + *filename);
        netConfStream.close();
        throw ex;
    }
    string mailerInboxSizeString;
    if (getline(netConfStream, mailerInboxSizeString) == 0) {
        RuntimeException ex("Network: Unexpected end of file or file format.");
        netConfStream.close();
        throw ex;
    }
    Tokenizer toknzr(mailerInboxSizeString);
    if (toknzr.peek() == NULL) {
        RuntimeException ex("Got NULL string while parsing mailbox size.");
        netConfStream.close();
        throw ex;
    }
    int mailerInboxSize = atoi((*toknzr.next()).c_str());
    if (mailerInboxSize <= 0) {
        RuntimeException ex("Mailer inbox size in configuratrion file has to be > 0! Ignoring...");
        netConfStream.close();
        throw ex;
    }
    if (mailer!=NULL) {
        mailer->interrupt();
        delete mailer;
    }
    mailer = new Mailer(this,mailerInboxSize);
    if (network!=NULL) {
        network->killAll();
        delete network;
    }
    network = Network::createNetwork(netConfStream,this, mailer);
    mailer->setNetwork(network);
    mailer->start();
    netConfStream.close();
}

void Shell::start() {
    if (pthread_create(&self, NULL, &shell_wrapper_func, (void*) this) != 0) {
        ThreadCreationException ex("Failed to create Shell thread");
        throw ex;
    }
}

void Shell::print(string* s) {//for use of other threads
    toPrint.push(s);
    privateWakeUp(true);
}

void Shell::goToSleep() {//IMPORTANT: assume classlock already locked
    sleeping=true;
    while (sleeping || toPrint.size()>0) {//really wakeUp only if nothing to print
        while (toPrint.size()>0) {
            string* tmp=toPrint.pop();
            cout<<*tmp<<endl;
            delete tmp;
        }
        if (sleeping) {
            pthread_cond_wait(&classCond,&classLock);
        }
    }
    pthread_mutex_unlock(&classLock);
}

void Shell::wakeUp() {//For use of other classes
    privateWakeUp(false);
}

void Shell::privateWakeUp(bool continueSleeping) {
    pthread_mutex_lock(&classLock);
    sleeping=continueSleeping;
    pthread_cond_signal(&classCond);
    pthread_mutex_unlock(&classLock);
}
